package com.uwantsoft.util.paging;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.orm.hibernate4.HibernateCallback;
import org.springframework.orm.hibernate4.HibernateTemplate;


import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

/**
 * User: Zhong Gang
 */
public abstract class AbstractQuerier<T> implements Querier<T> {
    final static Logger log = LogManager.getLogger(AbstractQuerier.class);

    private List<Filter> filters = new ArrayList<Filter>();
    private List<SortCriterionFilter> sortCriterionFilters = new ArrayList<SortCriterionFilter>();

    private HibernateTemplate hibernateTemplate;

    public AbstractQuerier() {
    }

    protected AbstractQuerier(HibernateTemplate hibernateTemplate) {
        this.hibernateTemplate = hibernateTemplate;
    }

    public void addFilter(Filter filter) {
        if (filter != null) {
            filters.add(filter);
        }
    }

    /**
     * Do not use add any <code>order by </code> key words in the <code>getSubHql</code> method,
     * just add the sort filed and sort direction.
     * <p/>
     * <code>
     * addSortCriterionFilter(new SortCriterionFilter() {
     * public String getSubHql() {
     * return " p.createdDateTime desc ";
     * }
     * });
     * </code>
     * <p/>
     * SortCriterionFilter only use when call {@link #getResults()} .
     *
     * @param sortCriterionFilter SortCriterionFilter instance
     */
    public void addSortCriterionFilter(SortCriterionFilter sortCriterionFilter) {
        if (sortCriterionFilter != null) {
            this.sortCriterionFilters.add(sortCriterionFilter);
        }
    }

    public List<Filter> getFilters() {
        return filters;
    }


    public String getSubHql() {
        StringBuilder subHql = new StringBuilder();
        List<Filter> filters = getFilters();
        for (Filter filter : filters) {
            subHql.append(filter.getSubHql());
        }
        return subHql.toString();
    }


    public String getSortHql() {
        StringBuilder sortHql = new StringBuilder();
        if (!this.sortCriterionFilters.isEmpty()) {
            sortHql.append(" order by ");
            int lastFilterIndex = sortCriterionFilters.size() - 1;
            for (SortCriterionFilter sortCriterionFilter : sortCriterionFilters) {
                sortHql.append(sortCriterionFilter.getSubHql());
                int currentIndex = sortCriterionFilters.indexOf(sortCriterionFilter);
                if (currentIndex != lastFilterIndex) {
                    sortHql.append(",");
                }
            }
        }
        return sortHql.toString();
    }

    public abstract String getResultHql();

    public int getStartPosition() {
        return 0;
    }

    public int getItemsAmountPerPage() {
        return 0;
    }

    public String getAmountHql() {
        throw new UnsupportedOperationException("Not yet implemented");
    }

    public Integer getAmount() {
        final String amountHql = getAmountHql();
        log.debug("Amount hql:" + amountHql);

        return (Integer)this.hibernateTemplate.execute(new HibernateCallback() {

            public Integer doInHibernate(Session session) throws HibernateException {
                Query query = createQuery(session, amountHql);
                return ((Long) query.uniqueResult()).intValue();
            }
        });
    }

    @SuppressWarnings("unchecked")
    public List<T> getResults() {
        final String resultHql = getResultHql() + getSortHql();
        log.debug("Result sql: " + resultHql);

        return (List<T>) this.hibernateTemplate.execute(new HibernateCallback() {

            public List<T> doInHibernate(Session session) throws HibernateException {
                Query query = createQuery(session, resultHql);
                int amountPerPage = getItemsAmountPerPage();
                if (amountPerPage == 0) {
                    return query.list();
                }
                int startPosition = getStartPosition();
                return query.setMaxResults(amountPerPage).setFirstResult(startPosition).list();
            }
        });

    }


    @SuppressWarnings("unchecked")
    public T uniqueResult() {
        final String resultHql = getResultHql();
        log.debug("Result sql: " + resultHql);

        return (T) this.hibernateTemplate.execute(new HibernateCallback() {

            public T doInHibernate(Session session) throws HibernateException {
                Query query = createQuery(session, resultHql);
                return (T) query.uniqueResult();
            }
        });
    }

    private Query createQuery(Session session, String resultHql) {
        Query query = session.createQuery(resultHql);
        List<Filter> filters = getFilters();
        for (Filter filter : filters) {
            if (filter instanceof ParameterFilter) {
                ParameterFilter parameterFilter = (ParameterFilter) filter;
                parameterFilter.setParameter(query);
            }
        }
        return query;
    }
}
